#include <fermat/dio/syscall_file.h>
#include <fermat/dio/iostats.h>
#include <fermat/dio/serving_request.h>
#include <fermat/dio/disk_queues.h>
#include <fermat/common/scoped_lock.h>
#include <cstring>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>


namespace fermat {

SyscallFile::SyscallFile(
	const std::string &filename,
	int mode,
	int queue_id,
	unsigned int device_id  )
	:FileBase(device_id),
	 _fd_mutex(),
 	 _fd(-1),
	 _mode(mode),
	 _queue_id(queue_id),
	 _path(filename),
	 _is_device(false)
{
	int flags = 0;
	if (mode & RDONLY)    {
		flags |= O_RDONLY;
	}

	if (mode & WRONLY)    {
		flags |= O_WRONLY;
	}

	if (mode & RDWR)    {
		flags |= O_RDWR;
	}

	if (mode & CREAT)    {
		flags |= O_CREAT;
	}

	if (mode & TRUNC)    { 
		flags |= O_TRUNC;    
	}
	if ((mode & DIRECT) || (mode & REQUIRE_DIRECT)) {
		flags |= O_DIRECT;
	}

	if (mode & SYNC)    {
		flags |= O_RSYNC;
		flags |= O_DSYNC;
		flags |= O_SYNC;
	}

	const int perms = S_IREAD | S_IWRITE | S_IRGRP | S_IWGRP;

	if ((_fd = ::open(filename.c_str(), flags, perms)) >= 0)    {
		_need_alignment = (mode & DIRECT) != 0;
		return;
	} 

	if ((mode & DIRECT) && !(mode & REQUIRE_DIRECT) && errno == EINVAL)    {
		flags &= ~O_DIRECT;
		_mode &= ~DIRECT;
		if ((_fd = ::open(filename.c_str(), flags, perms)) >= 0)        {
			return;
		}
	}

	_saved_errno = errno;
	
}

 RequestPtr SyscallFile::aread(
	 void* buffer, offset_type offset, size_type bytes,
	 const CompletionHandler& on_cmpl )
{
	RequestPtr req(new ServingRequest(on_cmpl, shared_from_this(), buffer, offset, bytes, Request::READ));
	DiskQueues::getInstance()->AddRequest(req, get_queue_id());
	return req;
}    

RequestPtr SyscallFile::awrite(
	 void* buffer, offset_type offset, size_type bytes,
	 const CompletionHandler& on_cmpl)
{
	RequestPtr req(new ServingRequest(on_cmpl, shared_from_this(), buffer, offset, bytes, Request::WRITE));
	DiskQueues::getInstance()->AddRequest(req, get_queue_id());
	return req;
}

 int SyscallFile::serve(void* buffer, offset_type offset, size_type bytes,
					Request::ReadOrWriteType type)
 {
 	ScopedMutex slock(&_fd_mutex);
 	char* cbuffer = static_cast<char*>(buffer);
	Stats::ScopedReadWriteTimer read_write_timer(bytes, type == Request::WRITE);
	int rc;
	while( bytes > 0) {
		if(type == Request::READ) {

			 rc = ::pread(_fd, cbuffer, bytes, offset );
			if(rc <= 0) {
				_saved_errno = errno;
				break;
			}
			bytes -= rc;
			offset += rc;
			cbuffer += rc;

			if(offset == _size()) {
				memset(cbuffer, 0, bytes);
				bytes = 0;
			}
			
		} else {
			rc = ::pwrite(_fd, cbuffer, bytes, offset );
			if(rc <= 0) {
				_saved_errno = errno;
				break;
			}
			bytes -= rc;
			offset += rc;
			cbuffer += rc;
			
		} 
	}//while

	return _saved_errno;
 }

SyscallFile::size_type SyscallFile::_size()
 {
 	    off_t rc = ::lseek(_fd, 0, SEEK_END);   
		if (rc < 0) {
			_saved_errno = errno;
			return 0;
		}
		return rc;

 }
void SyscallFile::set_size(offset_type newsize)
{
	ScopedMutex slock(&_fd_mutex);
	_set_size(newsize);
}

SyscallFile::offset_type SyscallFile::size()
  {
  	ScopedMutex slock(&_fd_mutex);
		return _size();
  }

void SyscallFile::_set_size(size_t newsize)
{
	if((!_is_device) && (!(_mode&RDONLY))) {
		int ret = ::ftruncate(_fd, newsize);
		(void)ret;
	}
}

void SyscallFile::lock()
{
	ScopedMutex slock(&_fd_mutex);
	struct flock lock_struct;
	lock_struct.l_type = (short)(_mode & RDONLY ? F_RDLCK : F_RDLCK | F_WRLCK);
	lock_struct.l_whence = SEEK_SET;
	lock_struct.l_start = 0;
	lock_struct.l_len = 0; // lock all bytes
	if ((::fcntl(_fd, F_SETLK, &lock_struct)) < 0) {
		_saved_errno = errno;
	}
}
void SyscallFile::close()
{
	ScopedMutex slock(&_fd_mutex);
	if(_fd == -1) {
		return ;
	}
	::close(_fd);
	_fd = -1;
}
void SyscallFile::close_remove()
{
	close();
	if(_is_device) {
		return ;
	}
	::remove(_path.c_str());
}

const char * SyscallFile::io_type() const
{
	return "syscall";
}
int SyscallFile::unlink()
{
	if((!_is_device) && (!(_mode&RDONLY))) {
		::unlink(_path.c_str());
	}
	return 0;
}
int SyscallFile::truncate( size_t length)
{
	set_size(length);
	return 0;	
}

}
